package bluknite.util.network;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.logging.Logger;

import bluknite.util.network.exception.NetworkException;

/**
 * This class provides utility methods for starting a {@code TCPServer} instance
 * and establishing {@code TCPConnection} connections to {@code TCPServer}
 * instances.
 * @author Siraj Ahmed
 */
public class TCPManager {
	private static final int SOCKET_TIMEOUT = 5000;
	private static final int PORT_C = 42422;
	
	private static Logger logger = Logger.getLogger(TCPManager.class.getName());
	
	/**
	 * Establishes a handshake connection with the {@code TCPServer} instance located
	 * at {@code host:port}.<br>
	 * This uses the following defaults:<br>
	 * <ul>
	 * <li>timeout value of {@code 5} seconds.</li>
	 * <li>local port of {@code 42422}.</li>
	 * </ul>
	 * @param host
	 * @param port
	 * @param handler the {@code TCPConnectionHandler} instance for handling recieve
	 * events.
	 * @return a {@link TCPConnection} instance.
	 * @throws NetworkException if there was an error establishing the
	 * connection.
	 */
	public static TCPConnection connect(String host, int port, TCPConnectionHandler handler) throws NetworkException {
		return connect(host, port, PORT_C, handler, SOCKET_TIMEOUT);
	}
	
	/**
	 * Establishes a handshake connection with the {@code TCPServer} instance located
	 * at {@code host:port}.<br>
	 * This uses the following defaults:<br>
	 * <ul>
	 * <li>local port of {@code 42422}.</li>
	 * </ul>
	 * @param host
	 * @param port
	 * @param handler the {@code TCPConnectionHandler} instance for handling recieve
	 * events.
	 * @param timeout the timeout in ms.
	 * @return a {@link TCPConnection} instance.
	 * @throws NetworkException if there was an error establishing the
	 * connection.
	 */
	public static TCPConnection connect(String host, int port, TCPConnectionHandler handler, int timeout) throws NetworkException {
		return connect(host, port, PORT_C, handler, timeout);
	}
	
	/**
	 * Establishes a handshake connection with the {@code TCPServer} instance located
	 * at {@code host:port}.<br>
	 * This uses the following defaults:<br>
	 * <ul>
	 * <li>timeout value of {@code 5} seconds.</li>
	 * </ul>
	 * @param host
	 * @param port
	 * @param localPort the local port at which to accept the handshake connection
	 * from the {@code TCPServer}.
	 * @param handler the {@code TCPConnectionHandler} instance for handling recieve
	 * events.
	 * @return a {@link TCPConnection} instance.
	 * @throws NetworkException if there was an error establishing the
	 * connection.
	 */
	public static TCPConnection connect(String host, int port, int localPort, TCPConnectionHandler handler) throws NetworkException {
		return connect(host, port, localPort, handler, SOCKET_TIMEOUT);
	}
	
	/**
	 * Establishes a connection with the {@code TCPServer} instance located
	 * at {@code host:port} using the specified {@code timeout} value.
	 * @param host
	 * @param port
	 * @param localPort the local port at which to accept the handshake connection
	 * from the {@code TCPServer}.
	 * @param handler the {@code TCPConnectionHandler} instance for handling recieve
	 * events.
	 * @param timeout the timeout in ms.
	 * @return a {@link TCPConnection} instance.
	 * @throws NetworkException if there was an error establishing the
	 * connection.
	 */
	public static TCPConnection connect(String host, int port, int localPort, TCPConnectionHandler handler, int timeout) throws NetworkException {
		if(host == null) {
			throw new NetworkException("Host name cannot be null.");
		}
		InetSocketAddress address = new InetSocketAddress(host, port);
		Socket in = new Socket();
		Socket out = new Socket();
		try {
			logger.info("Connecting in socket to server at: " + host + ":" + port + "...");
			in.connect(address, timeout);
			logger.info("Creating ObjectInputStream from server...");
			ObjectInputStream is = new ObjectInputStream(in.getInputStream());
			logger.info("Waiting for server port for establishing outbound socket...");
			int newPort = is.readInt();
			InetSocketAddress newAddress = new InetSocketAddress(host, newPort);
			logger.info("Connecting out socket to server at: " + host + ":" + newPort + "...");
			out.connect(newAddress, timeout);
			logger.info("Connection complete.");
		} catch (IOException e) {
			logger.warning(e.getMessage());
			logger.severe("Failure connecting to " + host + ":" + port);
			throw new NetworkException("");
		}
		return new TCPConnection(in, out, handler);
	}
}
